﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Linq;
using System.Web;

namespace Koolwired.Imap.ForDummies
{
    /// <summary>
    /// A simplified version of a (mailbox)message class, encapsulates ImapMailboxMessage.
    /// Keeps a reference to the mailbox and can therefor manage it's own operations in a
    /// OO manner.
    /// </summary>
    public class MailboxMessage
    {
        internal Mailbox mailbox;
        internal ImapMailboxMessage imapMailboxMessage;

        /// <summary>
        /// Constructor to instantiate an object from class, and set references to the original ImapMailboxMessage and Mailbox.
        /// By definition not instantiated bij the application but always through the Mailbox -class, and therefore only accessible internally.
        /// </summary>
        /// <param name="mailbox"></param>
        /// <param name="imapMailboxMessage"></param>
        internal MailboxMessage(Mailbox mailbox, ImapMailboxMessage imapMailboxMessage)
        {
            this.mailbox = mailbox;
            this.imapMailboxMessage = imapMailboxMessage;
        }

        /// <summary>
        /// Returns the messagebody in plain text, converting it from HTML when necessary
        /// </summary>
        /// <returns>Body text of message in plain text format</returns>
        public string GetPlainBodyText()
        {
            string result = "";
            if (imapMailboxMessage != null)
            {
                if (imapMailboxMessage.UID == null) FetchUIDAndTextBodyParts();
                if (imapMailboxMessage.HasText)
                {
                    result = imapMailboxMessage.BodyParts[imapMailboxMessage.Text].Data;
                }
                else
                    if (imapMailboxMessage.HasHTML)
                    {
                        result = imapMailboxMessage.BodyParts[imapMailboxMessage.HTML].Data;
                        if (imapMailboxMessage.BodyParts[imapMailboxMessage.HTML].ContentEncoding.Equals(BodyPartEncoding.BASE64))
                        {
                            result = Encoding.UTF8.GetString(Convert.FromBase64String(result));
                        }
                        result = ConvertHTMLToPlainText(result);
                    }
            }
            return result;
        }

        /// <summary>
        /// Decodes mime-encoded string
        /// </summary>
        /// <param name="mimeString">Mime-encoded string</param>
        /// <returns>Decoded  string</returns>
        internal string DecodeMimeString(string mimeString)
        {
            var regex = new Regex(@"=\?(?<charset>.*?)\?(?<encoding>[qQbB])\?(?<value>.*?)\?=");
            var encodedString = mimeString;
            var decodedString = string.Empty;

            while (encodedString.Length > 0)
            {
                var match = regex.Match(encodedString);
                if (match.Success)
                {
                    // If the match isn't at the start of the string, copy the initial few chars to the output
                    decodedString += encodedString.Substring(0, match.Index);

                    var charset = match.Groups["charset"].Value;
                    var encoding = match.Groups["encoding"].Value.ToUpper();
                    var value = match.Groups["value"].Value;

                    if (encoding.Equals("B"))
                    {
                        // Encoded value is Base-64
                        var bytes = Convert.FromBase64String(value);
                        decodedString += Encoding.GetEncoding(charset).GetString(bytes);
                    }
                    else if (encoding.Equals("Q"))
                    {
                        // Encoded value is Quoted-Printable
                        // Parse looking for =XX where XX is hexadecimal
                        var regx = new Regex("(\\=([0-9A-F][0-9A-F]))", RegexOptions.IgnoreCase);
                        decodedString += regx.Replace(value, new MatchEvaluator(delegate(Match m)
                        {
                            var hex = m.Groups[2].Value;
                            var iHex = Convert.ToInt32(hex, 16);

                            // Return the string in the charset defined
                            var bytes = new byte[1];
                            bytes[0] = Convert.ToByte(iHex);
                            return Encoding.GetEncoding(charset).GetString(bytes);
                        }));
                        decodedString = decodedString.Replace('_', ' ');
                    }
                    else
                    {
                        // Encoded value not known, return original string
                        // (Match should not be successful in this case, so this code may never get hit)
                        decodedString += encodedString;
                        break;
                    }

                    // Trim off up to and including the match, then we'll loop and try matching again.
                    encodedString = encodedString.Substring(match.Index + match.Length);
                }
                else
                {
                    // No match, not encoded, return original string
                    decodedString += encodedString;
                    break;
                }
            }
            return decodedString;
        }

        /// <summary>
        /// Internal method that fetches the UID and Bodyparts necessary to get to the messagebody.
        /// Encapsulating the IMAP specific complexity while preserving the performance gain by just
        /// retrieving the messageheaders initially and the rest of the message only when needed.
        /// </summary>
        void FetchUIDAndTextBodyParts()
        {
            if (imapMailboxMessage.UID == null)
            {
                imapMailboxMessage.UID = mailbox.imapCommand.FetchUID(imapMailboxMessage.ID).ToString();
                imapMailboxMessage = mailbox.imapCommand.FetchBodyStructure(imapMailboxMessage);
                if (imapMailboxMessage.HasText)
                {
                    imapMailboxMessage = mailbox.imapCommand.FetchBodyPart(imapMailboxMessage, imapMailboxMessage.Text);
                }
                if (imapMailboxMessage.HasHTML)
                {
                    imapMailboxMessage = mailbox.imapCommand.FetchBodyPart(imapMailboxMessage, imapMailboxMessage.HTML);
                }
            }
        }

        /// <summary>
        /// Deletes this message from the inbox, this does not only flag the messgae for deletion but
        /// does really delete it (so it calls IMAP:Expunge)
        /// </summary>
        /// <returns></returns>
        public bool Delete()
        {
            if (mailbox.imapCommand.SetDeleted(imapMailboxMessage.ID, true))
            {
                mailbox.imapCommand.Expunge();
                mailbox.ClearMessages();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Returns a generic list of attachments
        /// </summary>
        public List<MailboxMessageAttachment> Attachments
        {
            get
            {
                return (from bp in imapMailboxMessage.BodyParts
                        where bp.Attachment.Equals(true)
                        select new MailboxMessageAttachment(this, bp)).ToList();
            }
        }

        /// <summary>
        /// Saves all the attachments in this message to the path passed as 'pathPrefix' -parameter 
        /// </summary>
        /// <param name="pathPrefix">Specifies the filelocation where the attachments are saved, and provides a prefix to the filename. 
        /// For example: "C:\Attachments\message123_"</param>
        /// <returns>Result if the operation was succesfull</returns>
        public bool SaveAttachments(string pathPrefix)
        {
            foreach (var attachment in Attachments)
            {
                if (!attachment.Save(pathPrefix))
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// This private method will convert the HTML messagebody text to plain text by removing HTML tags,
        /// and decoding the HTML equivalents and then fixes the plain text spacing in order to compact the text.
        /// </summary>
        /// <param name="htmlText">The HTML-formatted textstring to convert</param>
        /// <returns>The string converted to plain text</returns>
        private string ConvertHTMLToPlainText(string htmlText)
        {
            //remove head and style sections completely
            htmlText = (new Regex(@"<head>[\S\s]*</head>", RegexOptions.IgnoreCase | RegexOptions.Multiline)).Replace(htmlText, "");
            htmlText = (new Regex(@"<style>[\S\s]*</style>", RegexOptions.IgnoreCase | RegexOptions.Multiline)).Replace(htmlText, "");
            // convert HTML style breaks to plain text line breaks
            htmlText = (new Regex(@"<(tr|br|p)\s*[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Multiline)).Replace(htmlText, "\r\n");
            htmlText = (new Regex(@"<(td)\s*[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Multiline)).Replace(htmlText, " ");
            // remove all HTML tags individually
            htmlText = (new Regex(@"<[^>]+>", RegexOptions.IgnoreCase | RegexOptions.Multiline)).Replace(htmlText, "");
            //decode HTML equivalents
            htmlText = HttpUtility.HtmlDecode(htmlText);
            //remove tabs
            htmlText = htmlText.Replace("\t", "");
            //remove whitespace at end of lines
            htmlText = (new Regex(@"^\s*$\n", RegexOptions.IgnoreCase | RegexOptions.Multiline)).Replace(htmlText, "\r\n");
            //remove whitespace at start and end of the text
            htmlText = htmlText.Trim('\n', '\r', ' ');
            //make sure that multiple blank lines are compacted into one empty line
            while (htmlText.IndexOf("\r\n\r\n\r\n") >= 0)
            {
                htmlText = htmlText.Replace("\r\n\r\n\r\n", "\r\n\r\n");
            }
            return htmlText;
        }

        /// <summary>
        /// Gets or sets the ImapMessageFlags for a ImapMessage.
        /// </summary>
        public ImapMessageFlags Flags
        {
            get { return imapMailboxMessage.Flags; }
            set { Flags = value; }
        }
        /// <summary>
        /// Gets or sets the ImapAddressCollection for a ImapMessage.
        /// </summary>
        public ImapAddressCollection Addresses
        {
            get { return imapMailboxMessage.Addresses; }
            set { imapMailboxMessage.Addresses = value; }
        }
        /// <summary>
        /// Gets the from address from the ImapAddressCollection.
        /// </summary>
        public ImapAddress From
        {
            get { return Addresses.From; }
        }
        /// <summary>
        /// Gets the reply to address from the ImapAddressCollection.
        /// </summary>
        public ImapAddress ReplyTo
        {
            get { return Addresses.ReplyTo; }
        }
        /// <summary>
        /// Gets the sender address from the ImapAddressCollection.
        /// </summary>
        public ImapAddress Sender
        {
            get { return Addresses.Sender; }
        }
        /// <summary>
        /// Gets the to addresses from the ImapAddressCollection.
        /// </summary>
        public ImapAddressList To
        {
            get { return Addresses.To; }
        }
        /// <summary>
        /// Gets the CC addresses from the ImapAddressCollection.
        /// </summary>
        public ImapAddressList CC
        {
            get { return Addresses.CC; }
        }
        /// <summary>
        /// Gets the BCC addresses from the ImapAddressCollection.
        /// </summary>
        public ImapAddressList BCC
        {
            get { return Addresses.BCC; }
        }
        /// <summary>
        /// Get message number
        /// </summary>
        public int ID
        {
            get { return imapMailboxMessage.ID; }
            set { imapMailboxMessage.ID = value; }
        }
        /// <summary>
        /// Gets or sets the UID of the message.
        /// </summary>
        public string UID
        {
            get { return imapMailboxMessage.UID; }
            set { imapMailboxMessage.UID = value; }
        }
        /// <summary>
        /// Get date message was received.
        /// </summary>
        public DateTime Received
        {
            get { return imapMailboxMessage.Received; }
            set { imapMailboxMessage.Received = value; }
        }
        /// <summary>
        /// Get integer value indicating the size of the message.
        /// </summary>
        public int Size
        {
            get { return imapMailboxMessage.Size; }
            set { imapMailboxMessage.Size = value; }
        }
        /// <summary>
        /// Get date time value indicating the date and time the message was sent.
        /// </summary>
        public DateTime Sent
        {
            get { return imapMailboxMessage.Sent; }
            internal set { imapMailboxMessage.Sent = value; }
        }
        /// <summary>
        /// Get string value indicating the time zone.
        /// </summary>
        public string TimeZone
        {
            get { return imapMailboxMessage.TimeZone; }
            internal set { imapMailboxMessage.TimeZone = value; }
        }
        /// <summary>
        /// Get string value indicating the Message ID
        /// </summary>
        public string MessageID
        {
            get { return imapMailboxMessage.MessageID; }
            set { imapMailboxMessage.MessageID = value; }
        }
        /// <summary>
        /// Get string value indicating what Message ID the Message refrences.
        /// </summary>
        public string Reference
        {
            get { return imapMailboxMessage.Reference; }
            set { imapMailboxMessage.Reference = value; }
        }
        /// <summary>
        /// Gets a string value containing the message subject.
        /// </summary>
        public string Subject
        {
            get { return imapMailboxMessage.Subject; }
            internal set { imapMailboxMessage.Subject = value; }
        }
        /// <summary>
        /// Gets message body parts that have been parsed.
        /// </summary>
        public ImapMessageBodyPartList BodyParts
        {
            get { return imapMailboxMessage.BodyParts; }
            internal set { imapMailboxMessage.BodyParts = value; }
        }

    }
}
